package ar.uba.dc.tl.mylanga.ui.console;

import ar.uba.dc.tl.mylanga.inputhandler.CommandLineInputHandler;
import ar.uba.dc.tl.mylanga.inputhandler.ConsoleInputHandler;
import ar.uba.dc.tl.mylanga.inputhandler.FileInputHandler;
import ar.uba.dc.tl.mylanga.inputhandler.Input;
import ar.uba.dc.tl.mylanga.inputhandler.InputHandler;
import ar.uba.dc.tl.mylanga.interpreter.Interpreter;
import ar.uba.dc.tl.mylanga.lexer.Lexer;
import ar.uba.dc.tl.mylanga.node.Start;
import ar.uba.dc.tl.mylanga.outputgenerator.CsvOutputGenerator;
import ar.uba.dc.tl.mylanga.parser.Parser;
import ar.uba.dc.tl.mylanga.semantic.SemanticAnalyzer;

import java.io.PushbackReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MyLanga {

	private PushbackReader reader;
	private List<String> arguments;
	private Map<Input, InputHandler> inputHandlers;

	private Input selectedInput;
	private InputHandler selectedInputHandler;

	public MyLanga(String[] arguments) {
		assert arguments != null;

		this.initializeInputHandlers();
		this.arguments = new ArrayList<>(Arrays.asList(arguments));
	}

	private Input getSelectedInput() {
		for (Input input : this.inputHandlers.keySet()) {
			if (this.isArgumentInput(input.getCommandLineSwitch())) {
				return input;
			}
		}

		throw new RuntimeException("invalid input");
	}

	private boolean isArgumentInput(String commandLineSwitch) {
		int index = this.arguments.indexOf(commandLineSwitch);

		return index != -1;
	}

	private void initializeCommandLineInputHandler() {
		Input input = new Input("Command Line", "-e");
		InputHandler inputHandler = new CommandLineInputHandler(input);

		this.inputHandlers.put(input, inputHandler);
	}

	private void initializeConsoleInputHandler() {
		Input input = new Input("Console", "-c");
		InputHandler inputHandler = new ConsoleInputHandler(input);

		this.inputHandlers.put(input, inputHandler);
	}

	private void initializeFileInputHandler() {
		Input input = new Input("File", "-f");
		InputHandler inputHandler = new FileInputHandler(input);

		this.inputHandlers.put(input, inputHandler);
	}

	private void initializeInputHandlers() {
		this.inputHandlers = new HashMap<>();

		this.initializeCommandLineInputHandler();
		this.initializeConsoleInputHandler();
		this.initializeFileInputHandler();
	}

	public void run() {
		this.processArguments();
		this.initializeReader();

		try {
			// Create the Lexer.
			Lexer lexer = new Lexer(this.reader);

			// Create the Parser.
			Parser parser = new Parser(lexer);

			// Parse input and generate AST.
			Start ast = parser.parse();

			// Create the Semantic Analyzer.
			SemanticAnalyzer semanticAnalyzer = new SemanticAnalyzer(ast.getPProgram());

			// Perform semantic analysis.
			semanticAnalyzer.check();

			// Create the Interpreter.
			Interpreter interpreter = new Interpreter();

			// Interpret the AST.
			ast.apply(interpreter);

			// Output the generated points.
			// TODO: Add command line option to select output handler and output filename.
			CsvOutputGenerator outputGenerator = new CsvOutputGenerator(interpreter.getPoints());

			outputGenerator.generate("results.csv");
		} catch (Exception e) {
			// TODO: Add more specific exceptions and avoid printing the stack trace.
			e.printStackTrace();
		}
	}

	private void initializeReader() {
		this.reader = this.selectedInputHandler.getReader();
	}

	private void processArguments() {
		this.initializeSelectedInput();
		this.initializeSelectedInputHandler();

		this.selectedInputHandler.setArguments(this.arguments);
		this.selectedInputHandler.checkArguments();
	}

	private void initializeSelectedInput() {
		this.selectedInput = this.getSelectedInput();
	}

	private void initializeSelectedInputHandler() {
		this.selectedInputHandler = this.inputHandlers.get(this.selectedInput);
	}
}
